package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
import com.jzo2o.api.orders.dto.request.OrderCancelReqDTO;
import com.jzo2o.api.orders.dto.response.OrderResDTO;
import com.jzo2o.api.orders.dto.response.OrderSimpleResDTO;
import com.jzo2o.api.trade.RefundRecordApi;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.api.trade.enums.RefundStatusEnum;
import com.jzo2o.common.constants.UserType;
import com.jzo2o.common.enums.EnableStatusEnum;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.common.utils.JsonUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.mysql.utils.PageUtils;
import com.jzo2o.orders.base.config.OrderStateMachine;
import com.jzo2o.orders.base.constants.RedisConstants;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderRefundStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusChangeEventEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
import com.jzo2o.orders.base.model.domain.OrdersRefund;
import com.jzo2o.orders.base.model.dto.OrderSnapshotDTO;
import com.jzo2o.orders.base.model.dto.OrderUpdateStatusDTO;
import com.jzo2o.orders.base.service.IOrdersCommonService;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import com.jzo2o.orders.manager.model.dto.request.OrderPageQueryReqDTO;
import com.jzo2o.orders.manager.service.IOrdersCanceledService;
import com.jzo2o.orders.manager.service.IOrdersManagerService;
import com.jzo2o.orders.manager.service.IOrdersRefundService;
import com.jzo2o.orders.manager.service.client.MarketClient;
import com.jzo2o.redis.helper.CacheHelper;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.jzo2o.orders.base.constants.FieldConstants.SORT_BY;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersManagerServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersManagerService {

    @Resource
    private IOrdersCommonService ordersCommonService;
    @Resource
    private IOrdersCanceledService ordersCanceledService;
    @Resource
    private IOrdersRefundService ordersRefundService;
    @Resource
    private IOrdersManagerService currentProxy;
    @Resource
    private RefundRecordApi refundRecordApi;
    @Resource
    private OrderStateMachine orderStateMachine;
    @Resource
    private CacheHelper cacheHelper;
    @Resource
    private MarketClient marketClient;
    @Override
    public List<Orders> batchQuery(List<Long> ids) {
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery().in(Orders::getId, ids).ge(Orders::getUserId, 0);
        return baseMapper.selectList(queryWrapper);
    }

    @Override
    public Orders queryById(Long id) {
        return baseMapper.selectById(id);
    }

    /**
     * 滚动分页查询
     *
     * @param currentUserId 当前用户id
     * @param ordersStatus  订单状态，0：待支付，100：派单中，200：待服务，300：服务中，400：待评价，500：订单完成，600：已取消，700：已关闭
     * @param sortBy        排序字段
     * @return 订单列表
     */
    @Override
    public List<OrderSimpleResDTO> consumerQueryList(Long currentUserId, Integer ordersStatus, Long sortBy) {
        //1.构件查询条件
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery()
                .eq(ObjectUtils.isNotNull(ordersStatus), Orders::getOrdersStatus, ordersStatus)
                //滚动分页sortBy要小于上一页最小的的sortBy
                .lt(ObjectUtils.isNotNull(sortBy), Orders::getSortBy, sortBy)
                .eq(Orders::getUserId, currentUserId)
                .eq(Orders::getDisplay, EnableStatusEnum.ENABLE.getStatus())
                //查询时，只要select id
                .select(Orders::getId);
        Page<Orders> queryPage = new Page<>();
        queryPage.addOrder(OrderItem.desc(SORT_BY));
        //滚动查询不需要查询总数
        queryPage.setSearchCount(false);
        //2.查询订单id列表
        Page<Orders> ordersPage = baseMapper.selectPage(queryPage, queryWrapper);
        List<Orders> records = ordersPage.getRecords();
        if (CollUtils.isEmpty(records)) {
            return Collections.emptyList();
        }
        //3.从List<Orders>里取出List<订单id>，再找每个id对应的订单详情
        List<Long> ids = records.stream().map(Orders::getId).collect(Collectors.toList());
        /*//  遍历每个id：先根据订单id查询缓存，如果缓存有就直接使用缓存数据；如果缓存没有就需要查询详情，缓存起来
        for (Long id : ids) {
            //从当前用户的hash里查找id对应的订单详情
            //如果找不到：根据订单id查询数据库，将结果再缓存到hash里
            //将Orders对象转换成OrderSimpleResDTO
        }*/
        //参数1：缓存的key。将某个字符串转换为RedisConstants.RedisKey.ORDERS格式
        String redisKey = String.format(RedisConstants.RedisKey.ORDERS, currentUserId);
        //参数2：要查询的数据id集合
        //参数3：如果某个id对应的数据没有缓存，需要我们提供好查询数据库的方法，返回查询的结果，cacheHelper会将结果缓存起来
        //参数4：将最终返回List里的类型传进去
        return cacheHelper.batchGet(redisKey, ids, new CacheHelper.BatchDataQueryExecutor<Long, OrderSimpleResDTO>() {
            /**
             * 如果没有缓存，就会调用这个方法查询数据库获取数据
             * @param objectIds 没有缓存的id集合
             * @param clazz 每个id对应的数据对象类型
             * @return Map<id, 数据对象>，cacheHelper得到这个返回值，会将整个map直接存储到Redis的hash里
             */
            @Override
            public Map<Long, OrderSimpleResDTO> execute(List<Long> objectIds, Class<OrderSimpleResDTO> clazz) {
                //查询objectIds对应的订单详情列表
                List<Orders> orders = batchQuery(objectIds);
                //将List转换成Map<id，OrderSimpleResDTO对象>
                return orders.stream().collect(Collectors.toMap(Orders::getId, o -> BeanUtil.toBean(o, OrderSimpleResDTO.class)));
            }
        }, OrderSimpleResDTO.class, RedisConstants.Ttl.ORDERS_PAGE_TTL);
    }

    /**
     * 根据订单id查询
     *
     * @param id 订单id
     * @return 订单详情
     */
    @Override
    public OrderResDTO getDetail(Long id) {
        //找状态机的快照缓存
        String snapshotJson = orderStateMachine.getCurrentSnapshotCache(id.toString());
        //JsonUtils.toBean（）将字符串转换成OrderSnapshotDTO对象
        OrderSnapshotDTO snapshot = JsonUtils.toBean(snapshotJson, OrderSnapshotDTO.class);
        //如果此订单是未支付，且已经超时，就取消订单
        boolean canceled = cancelOverTimeNoPayOrder(snapshot);
        if (canceled) {
            snapshotJson = orderStateMachine.getCurrentSnapshotCache(id.toString());
            snapshot = JsonUtils.toBean(snapshotJson, OrderSnapshotDTO.class);
        }
        OrderResDTO orderResDTO = BeanUtil.toBean(snapshot, OrderResDTO.class);
        return orderResDTO;
    }

    private boolean cancelOverTimeNoPayOrder(OrderSnapshotDTO orders) {
        if (ObjectUtils.equal(orders.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())
                && orders.getCreateTime().isBefore(LocalDateTime.now().minusMinutes(15))) {
            OrderCancelDTO dto = new OrderCancelDTO();
            dto.setId(orders.getId());
            dto.setCurrentUserType(UserType.SYSTEM);
            dto.setCancelReason("超时未支付自动取消订单");
            currentProxy.cancelNoPayOrder(dto);
            return true;
        }
        return false;
    }

    /**
     * 订单评价
     *
     * @param ordersId 订单id
     */
    @Override
    @Transactional
    public void evaluationOrder(Long ordersId) {
//        //查询订单详情
//        Orders orders = queryById(ordersId);
//
//        //构建订单快照
//        OrderSnapshotDTO orderSnapshotDTO = OrderSnapshotDTO.builder()
//                .evaluationTime(LocalDateTime.now())
//                .build();
//
//        //订单状态变更
//        orderStateMachine.changeStatus(orders.getUserId(), orders.getId().toString(), OrderStatusChangeEventEnum.EVALUATE, orderSnapshotDTO);
    }

    @Override
    public List<Orders> queryOverTimeNoPayOrdersByCount(int count) {
        return lambdaQuery()
                .eq(Orders::getPayStatus, OrderPayStatusEnum.NO_PAY.getStatus())
                .lt(Orders::getCreateTime, LocalDateTime.now().minusMinutes(15))
                //在SQL语句最后拼接上  limit 数量
                .last("limit " + count)
                .list();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelNoPayOrder(OrderCancelDTO dto) {
        //1. 修改订单的状态：update orders set order_status = 已取消 where id = 订单id
        OrderSnapshotDTO snapshot = new OrderSnapshotDTO();
        snapshot.setCancellerId(dto.getCurrentUserId());
        snapshot.setCancelerName(dto.getCurrentUserName());
        snapshot.setCancellerType(dto.getCurrentUserType());
        snapshot.setCancelReason(dto.getCancelReason());
        Orders orders = getById(dto.getId());
        orderStateMachine.changeStatus(orders.getUserId(), dto.getId().toString(), OrderStatusChangeEventEnum.CANCEL, snapshot);
        //2. 新增一条订单取消记录
        OrdersCanceled ordersCanceled = new OrdersCanceled();
        ordersCanceled.setId(dto.getId());
        ordersCanceled.setCancellerId(dto.getCurrentUserId());
        ordersCanceled.setCancellerType(dto.getCurrentUserType());
        ordersCanceled.setCancelerName(dto.getCurrentUserName());
        ordersCanceled.setCancelReason(dto.getCancelReason());
        ordersCanceled.setCancelTime(LocalDateTime.now());
        ordersCanceledService.save(ordersCanceled);
    }

    @Override
    public void cancel(OrderCancelReqDTO dto) {
        //查询订单
        Orders orders = getById(dto.getId());
        if (orders == null) {
            throw new BadRequestException("订单不存在");
        }

        //准备取消订单需要的参数
        OrderCancelDTO orderCancelDto = new OrderCancelDTO();
        orderCancelDto.setId(orders.getId());
        orderCancelDto.setCurrentUserId(UserContext.currentUserId());
        orderCancelDto.setCurrentUserName(UserContext.currentUser().getName());
        orderCancelDto.setCurrentUserType(UserContext.currentUser().getUserType());
        orderCancelDto.setCancelReason(dto.getCancelReason());

        //判断订单状态
        if (ObjectUtils.equal(orders.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())) {
            //要取消未支付的订单
               //进行判断有没有使用优惠卷
            if (orders.getDiscountAmount()!=null && orders.getDiscountAmount().compareTo(BigDecimal.ZERO) > 0){
                currentProxy.cancelNoPayOrderByUseCoupon(orderCancelDto);
            }else {
                currentProxy.cancelNoPayOrder(orderCancelDto);
            }
        } else if (ObjectUtils.equal(orders.getOrdersStatus(), OrderStatusEnum.DISPATCHING.getStatus())) {
            //准备数据：交易单id 和 退款金额
            orderCancelDto.setTradingOrderNo(orders.getTradingOrderNo());
            orderCancelDto.setRealPayAmount(orders.getRealPayAmount());
            //要取消派单中已支付的订单：修改订单状态、新增了取消记录、新增了待退款记录
               // 进行判断有没有使用优惠卷
            if (orders.getDiscountAmount()!=null && orders.getDiscountAmount().compareTo(BigDecimal.ZERO) > 0){
                currentProxy.cancelDispatchingOrderByUseCoupon(orderCancelDto);
            }else {
                currentProxy.cancelDispatchingOrder(orderCancelDto);
            }
            //申请退款：为防止取消派单中订单执行流程过长、耗时太久，用户体验不好，我们申请退款使用异步（采用线程方式）
            new Thread(() -> {
                requestRefundOrder(orders.getId(), orders.getRealPayAmount(), orders.getTradingOrderNo());
            }).start();
        } else {
            throw new ForbiddenOperationException("不支持取消订单");
        }
    }

    @Override
    public void requestRefundOrder(Long ordersId, BigDecimal realPayAmount, Long tradingOrderNo) {
        //1. 通过trade服务发起退款申请，得到退款操作的结果
        ExecutionResultResDTO res = refundRecordApi.refundTrading(tradingOrderNo, realPayAmount);
        //2. 处理退款的结果
        currentProxy.refundOrder(res, ordersId);
    }

    @Override
    public PageResult<OrderSimpleResDTO> pageQuery(OrderPageQueryReqDTO pageQuery) {
        // 1. 构建查询条件
        LambdaQueryWrapper<Orders> wrapper = Wrappers.<Orders>lambdaQuery()
                // 根据订单编号查询
                .eq(ObjectUtils.isNotEmpty(pageQuery.getId()), Orders::getId, pageQuery.getId())
                // 根据客户电话模糊查询
                .like(ObjectUtils.isNotEmpty(pageQuery.getContactsPhone()), Orders::getContactsPhone, pageQuery.getContactsPhone())
                // 根据下单时间范围查询
                .ge(ObjectUtils.isNotEmpty(pageQuery.getMinCreateTime()), Orders::getCreateTime, pageQuery.getMinCreateTime())
                .le(ObjectUtils.isNotEmpty(pageQuery.getMaxCreateTime()), Orders::getCreateTime, pageQuery.getMaxCreateTime())
                // 根据订单状态查询
                .eq(ObjectUtils.isNotEmpty(pageQuery.getOrdersStatus()), Orders::getOrdersStatus, pageQuery.getOrdersStatus())
                // 根据支付状态查询
                .eq(ObjectUtils.isNotEmpty(pageQuery.getPayStatus()), Orders::getPayStatus, pageQuery.getPayStatus())
                // 根据退款状态查询
                .eq(ObjectUtils.isNotEmpty(pageQuery.getRefundStatus()), Orders::getRefundStatus, pageQuery.getRefundStatus())
                // 默认按创建时间降序排序
                .orderByDesc(Orders::getCreateTime);
        //2.创建page对象
        Page<Orders> ordersPage = PageUtils.parsePageQuery(pageQuery, Orders.class);
        //3.调用mapper方法，封装对象返回
        Page<Orders> result = baseMapper.selectPage(ordersPage, wrapper);
        PageResult<OrderSimpleResDTO> pageResult = PageUtils.toPage(result, OrderSimpleResDTO.class);
        return pageResult;
    }
    @GlobalTransactional
    @Override
    public void cancelNoPayOrderByUseCoupon(OrderCancelDTO orderCancelDto) {
        CouponUseBackReqDTO dto = new CouponUseBackReqDTO();
        //todo 优惠卷id没处理
        dto.setOrdersId(orderCancelDto.getId());
        dto.setUserId(orderCancelDto.getCurrentUserId());
        //1.调用market服务，优惠卷退回
        marketClient.useBack(dto);
        //2.调用cancelNoPayOrder取消订单
        cancelNoPayOrder(orderCancelDto);
    }
    @GlobalTransactional
    @Override
    public void cancelDispatchingOrderByUseCoupon(OrderCancelDTO orderCancelDto) {
        CouponUseBackReqDTO dto = new CouponUseBackReqDTO();
        //todo 优惠卷id没处理
        dto.setOrdersId(orderCancelDto.getId());
        dto.setUserId(orderCancelDto.getUserId());
        //1.调用market服务，优惠卷退回
        marketClient.useBack(dto);
        //2.调用cancelDispatchingOrder取消订单
        cancelDispatchingOrder(orderCancelDto);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void refundOrder(ExecutionResultResDTO res, Long ordersId) {
        //1. 获取退款状态。这个状态是trade服务里状态值
        Integer refundStatus = res.getRefundStatus();

        //2. 判断退款的状态，如果是退款中状态，就直接结束
        if (ObjectUtils.equal(refundStatus, RefundStatusEnum.SENDING.getCode())) {
            return;
        }

        //3. 如果不是退款中，就需要完成退款后的处理
        //3.1 将trade服务返回的结果状态，转换成Orders表里自己枚举对应的状态
        if (ObjectUtils.equal(refundStatus, RefundStatusEnum.SUCCESS.getCode())) {
            refundStatus = OrderRefundStatusEnum.REFUND_SUCCESS.getStatus();
        } else if (ObjectUtils.equal(refundStatus, RefundStatusEnum.FAIL.getCode())) {
            refundStatus = OrderRefundStatusEnum.REFUND_FAIL.getStatus();
        }
        //3.2 修改orders表的状态
        boolean updated = lambdaUpdate()
                .set(Orders::getRefundStatus, refundStatus)
                .set(Orders::getRefundNo, res.getRefundNo())
                .set(Orders::getRefundId, res.getRefundId())
                .eq(Orders::getId, ordersId)
                .update();
        if (updated) {
            //3.3 删除待退款记录
            ordersRefundService.removeById(ordersId);
        }

        String snapshotJson = orderStateMachine.getCurrentSnapshot(ordersId.toString());
        OrderSnapshotDTO snapshot = JsonUtils.toBean(snapshotJson, OrderSnapshotDTO.class);
        snapshot.setThirdRefundOrderId(res.getRefundId());
        snapshot.setRefundNo(res.getRefundNo());
        snapshot.setRefundStatus(OrderRefundStatusEnum.REFUND_SUCCESS.getStatus());
        Orders orders = getById(ordersId);
        orderStateMachine.saveSnapshot(orders.getUserId(), ordersId.toString(), OrderStatusEnum.CLOSED, snapshot);
    }

    //ctrl+i：列出当前类里所有可以implements的方法
    //ctrl+o：列出当前类里所有可以override的方法
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelDispatchingOrder(OrderCancelDTO dto) {
        //1. 修改订单表的状态：把order_status修改为已关闭，把refund_status修改为退款中
        OrderSnapshotDTO snapshot = new OrderSnapshotDTO();
        snapshot.setCancellerId(dto.getCurrentUserId());
        snapshot.setCancelerName(dto.getCurrentUserName());
        snapshot.setCancellerType(dto.getCurrentUserType());
        snapshot.setCancelReason(dto.getCancelReason());
        Orders orders = getById(dto.getId());
        orderStateMachine.changeStatus(orders.getUserId(), dto.getId().toString(), OrderStatusChangeEventEnum.CLOSE_DISPATCHING_ORDER, snapshot);
        //2. 新增一条取消记录
        OrdersCanceled ordersCanceled = new OrdersCanceled();
        ordersCanceled.setId(dto.getId());
        ordersCanceled.setCancellerId(dto.getCurrentUserId());
        ordersCanceled.setCancelerName(dto.getCurrentUserName());
        ordersCanceled.setCancellerType(dto.getCurrentUserType());
        ordersCanceled.setCancelReason(dto.getCancelReason());
        ordersCanceled.setCancelTime(LocalDateTime.now());
        ordersCanceledService.save(ordersCanceled);
        //3. 新增一条待退款记录
        OrdersRefund ordersRefund = new OrdersRefund();
        ordersRefund.setTradingOrderNo(dto.getTradingOrderNo());
        ordersRefund.setRealPayAmount(dto.getRealPayAmount());
        ordersRefund.setId(dto.getId());
        ordersRefundService.save(ordersRefund);
    }
}
